<?php
//https://www.yuque.com/docs/share/e098fee0-0830-4555-ba92-56d2ea0bce73
namespace jy_sandpay;
defined('BY_JYA') or exit('error');
class pay extends \plugin{
    public function __construct($setting=array()){
        parent::__construct('jy_sandpay');
        $setting=uni_setting('pay');
        if(!$setting['sandpay_mid'] || !$setting['sandpay_key'] || !$setting['sandpay_private']  || !$setting['sandpay_public']){
            exi('商户号，私钥证书密码，私钥文件，公钥文件都必须填写','error');
        }
        if($_SESSION['auth_wxid']){
            $auth_appid=pdo_getcolumn('core_account_wechat',array('id'=>$_SESSION['auth_wxid']),'appid');
        }
        $wechat_appid=$auth_appid?:$setting['wechat_appid'];
        if(!$wechat_appid){
            $wechat_appid=pdo_getcolumn('core_account_wechat',array('uniacid'=>$_SESSION['uniacid'],'isdefault'=>1),'appid');
            if(!$wechat_appid){
                $wechat_appid=pdo_getcolumn('core_account_wechat',array('uniacid'=>$_SESSION['uniacid']),'appid');
            }
        }
        $this->config=array(
            'appid'=>$wechat_appid,
            'sandpay_mid'=>$setting['sandpay_mid'],
            'sandpay_key'=>$setting['sandpay_key'],
            'sandpay_key1'=>$setting['sandpay_key1'],
            'sandpay_key2'=>$setting['sandpay_key2'],
            'sandpay_md5key'=>$setting['sandpay_md5key'],
            'sandpay_wxappid'=>$setting['sandpay_wxappid'],
            'sandpay_env'=>$setting['sandpay_env'],
            'sandpay_private'=>ROOT_AT.$setting['sandpay_private'],
            'sandpay_public'=>ROOT_AT.$setting['sandpay_public'],
            'sandpay_hmmid'=>$setting['sandpay_hmmid'],
            'sandpay_hmkey'=>$setting['sandpay_hmkey'],
            'sandpay_rsa_key'=>$setting['sandpay_rsa_key'],
        );
    }
    //创建订单
    public function create($pay_log,$paytype='',$wxappid=''){
        # 支付设置
        if($paytype=='wechat'){
            if($_SESSION['container']=='wechat'){
                $paytype='wechatpay';
                $product_code='02010002';
                $expend=array(
                    'mer_app_id'=>$this->config['appid'],
                    'openid'=>$pay_log['openid']?:$_SESSION['openid'],
                );
                if(!$this->config['appid']) {
                    exi('未绑定微信公众号，不能使用微信支付！', 'error');
                }
            }elseif($_SESSION['container']=='wxapp'){
                $this->config['appid']=$wxappid?:$this->config['appid'];
                $paytype='sand_wx';
                $product_code='00002021';
                $expend=array(
                    'subAppid'=>$this->config['appid'],
                    'userId'=>$pay_log['openid']?:$_SESSION['openid'],
                );
            }else{
                if(!$this->config['sandpay_wxappid']) {
                    exi('非微信环境下，未填写小程序ID！', 'error');
                }
                if(!$this->config['sandpay_env']) {
                    exi('非微信环境下，未填写开发环境ID！', 'error');
                }
                $expend=array(
                    'resourceAppid'=>$this->config['sandpay_wxappid'],
                    'resourceEnv'=>$this->config['sandpay_env'],
                );
                $product_code='02010006';
                $paytype='applet';
            }
        }
        if($paytype=='alipay'){
            if(!$this->config['sandpay_rsa_key']) {
                exi('河马商户RSA私钥，未填！', 'error');
            }
            $paytype='alipay';
            $product_code='01020002';
        }
        if($paytype=='union_online'){
            $paytype='fastpayment';
            $product_code='05030001';
        }
        if($paytype=='alipay'){
            $expend=array(
                'buyer_id'=>'',
            );
        }elseif($paytype=='fastpayment'){
            $expend=array(
                'userId'=>$_SESSION['uid'],
            );
        }
        $payment_params = array(
            'product_code'=>$product_code,
            'mer_order_no'=> $pay_log['tid']?:$pay_log['ordersn'],
            'order_amt'=>$pay_log['fee'],
            'goods_name'=>$pay_log['title'],
            'pay_extra'=>json_encode($expend),
            'notify_url'=>$pay_log['notify_url']?:SITEROOT .'addons/jy_sandpay/notify.php',
            'return_url'=>custom_url($pay_log['return_url'],true),
        );
        $payment_params=$this->getdata($payment_params,$paytype);
        //print_r($payment_params);exit;
        # 发起支付
        $temp = $payment_params;
        unset($temp['goods_name']);
        unset($temp['jump_scheme']);
        unset($temp['expire_time']);
        unset($temp['product_code']);
        unset($temp['clear_cycle']);
        unset($temp['meta_option']);
        if($paytype=='alipay'){
            unset($temp['mer_key']);
            unset($temp['accsplit_flag']);
            unset($payment_params['mer_key']);
            unset($payment_params['accsplit_flag']);
            $temp['sign_type']=$payment_params['sign_type']='RSA';
            $payment_params['accsplit_info']=$temp['accsplit_info']='N';
            //file_put_contents(ROOT_D.'/log.txt', date('Y-m-d H:i:s', time()) . " 签名串:" . $this->getSignContent($temp). "\r\n", FILE_APPEND); // key对应商户私钥通过安卓APK工具解析出来的MD5KEY
            $sign = $this->rsaSign($this->getSignContent($temp), $this->config['sandpay_rsa_key'], 'RSA');
            $payment_params['sign'] = $sign;
            $payment_params['clear_cycle'] = '1';
            $query = http_build_query($payment_params);
            $payurl = "https://cash.sandgate.cn/h5/?".$query."#/hippoH5";
        }elseif($paytype=='sand_wx'){
            $apiurl='https://cashier.sandpay.com.cn/gateway/api/order/pay';
            $totalAmount=sprintf("%012d", $pay_log['fee']*100);
            $payment_params = array(
                'payMode'=>'sand_wx',
                'orderCode'=> $pay_log['tid']?:$pay_log['ordersn'],
                'totalAmount'=>$totalAmount,
                'subject'=>$pay_log['title'],
                'body'=>$pay_log['title'],
                'payExtra'=>$expend,
                'clientIp'=>CLIENT_IP,
                'notifyUrl'=>$pay_log['notify_url']?:SITEROOT .'addons/jy_sandpay/notify.php',
                'frontUrl'=>$pay_log['notify_url']?:SITEROOT .'addons/jy_sandpay/notify.php',
            );
            $res=$this->request($apiurl,$payment_params,'sandpay.trade.pay');
            $credential=json_decode($res['credential'],true);
            $credential=json_decode($credential['params'],true);
            file_put_contents(ROOT_D.'/log.txt', date('Y-m-d H:i:s', time()) . " 支付凭证:" . json_encode($credential)."\r\n", FILE_APPEND);
            $package=array(
                'appId'=>$credential['appId'],
                'timeStamp'=>$credential['timeStamp'],
                'package'=>'prepay_id='.$credential['prepay_id'],
                'signType'=>$credential['signType'],
                'nonceStr'=>$credential['nonceStr'],
            );
            $package['paySign']=$credential['paySign'];
            return $package;
        }else{
            $temp['store_id']=$payment_params['store_id']='000000';
            file_put_contents(ROOT_D.'/log.txt', date('Y-m-d H:i:s', time()) . " 签名串:" . $this->getSignContent($temp)."&key=".$this->config['sandpay_md5key'] . "\r\n", FILE_APPEND); // key对应商户私钥通过安卓APK工具解析出来的MD5KEY
            $sign = strtoupper(md5($this->getSignContent($temp)."&key=".$this->config['sandpay_md5key']));  // key对应商户私钥通过安卓APK工具解析出来的MD5KEY
            $payment_params['sign'] = $sign;
            $query = http_build_query($payment_params);
            $payurl = "https://sandcash.mixienet.com.cn/pay/h5/".$paytype."?".$query;
        }
        file_put_contents(ROOT_D.'/log.txt', date('Y-m-d H:i:s', time()) . " 地址:" . $payurl. "\r\n", FILE_APPEND);
        return $payurl; // 返回支付url
    }
    /*
        整理数据
    */
    private function getdata($payment_params,$paytype=''){
        $create_ip=CLIENT_IP;
        $create_ip=str_replace('.','_',$create_ip);
        $maindata=array(
            'version' => 10,
            'mer_no' =>  $this->config['sandpay_mid'],
            'mer_key' => $this->config['sandpay_key1'],
            'store_id' =>  '100001',
            'create_time' => date('YmdHis'),
            'expire_time' => date('YmdHis', time()+30*60),
            'create_ip' => $create_ip,
            'clear_cycle' => '3',
            'accsplit_flag' => 'NO',
            'jump_scheme' => 'sandcash://scpay',
            'meta_option' => json_encode([["s" => "Android","n" => "wxDemo","id" => "com.pay.paytypetest","sc" => "com.pay.paytypetest"]]),
            'sign_type' => 'MD5'
        );
        if($paytype=='alipay'){
            $maindata['mer_no']=$this->config['sandpay_hmmid'];
            $maindata['mer_key']=$this->config['sandpay_hmkey'];
        }
        $payment_params=array_merge($maindata,$payment_params);
        return $payment_params;
    }
    private function getSignContent($params) {
        ksort($params);
        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . "$v";
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . "$v";
                }
                $i++;
            }
        }
        unset ($k, $v);
        return $stringToBeSigned;
    }
    private function checkEmpty($value)
    {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;

        return false;
    }
    // 公钥
    private function publicKey()
    {
        try {
            $file = file_get_contents($this->config['sandpay_public']);
            if (!$file) {
                exi('公钥文件读取有误','error');
            }
            $cert   = chunk_split(base64_encode($file), 64, "\n");
            $cert   = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";
            $res    = openssl_pkey_get_public($cert);
            $detail = openssl_pkey_get_details($res);
            openssl_free_key($res);
            if (!$detail) {
                exi('公钥文件解析有误','error');
            }
            return $detail['key'];
        } catch (\Exception $e) {
            throw $e;
        }
    }

    // 私钥
    private function privateKey()
    {
        try {
            $file = file_get_contents($this->config['sandpay_private']);
            if (!$file) {
                exi('私钥文件读取有误','error');
            }
            if (!openssl_pkcs12_read($file, $cert, $this->config['sandpay_key'])) {
                exi('私钥密码错误','error');
            }
            return $cert['pkey'];
        } catch (\Exception $e) {
            throw $e;
        }
    }
    protected function rsaSign($data, $privateKey, $signType = "RSA")
    {
        $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($privateKey, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        ($res) or die('您使用的私钥格式错误，请检查RSA私钥配置');

        if ("RSA2" == $signType) {
            openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
        } else {
            openssl_sign($data, $sign, $res);
        }

        $sign = base64_encode($sign);
        return $sign;
    }
    // 私钥加签
    protected function sign($plainText)
    {
        $plainText = json_encode($plainText);
        file_put_contents(ROOT_D.'/log.txt', date('Y-m-d H:i:s', time()) . " sand_wx串:" . $plainText. "\r\n", FILE_APPEND);
        try {
            $resource = openssl_pkey_get_private($this->privateKey());
            $result   = openssl_sign($plainText, $sign, $resource);
            openssl_free_key($resource);
            if (!$result) {
                exi('签名失败！','error');
            }
            return base64_encode($sign);
        } catch (\Exception $e) {
            throw $e;
        }
    }
    /**
     * rsaCheckV2
     * 验证签名
     * sign_type参与签名
     **/
    public function rsaCheckV2($params){
        $sign = $params['sign'];
        unset($params['sign']);
        $rsaPublicKey=$this->config['sandpay_hmkey'];//河马公钥
        return $this->verifyV2($this->getSignContent($params), $sign, $rsaPublicKey, 'RSA');
    }
    public function verifyV2($data, $sign, $rsaPublicKey, $signType = 'RSA'){
        $pubKey = $rsaPublicKey;
        $res = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($pubKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        ($res) or die('RSA公钥错误。请检查公钥文件格式是否正确');
        //调用openssl内置方法验签，返回bool值
        if ("RSA2" == $signType) {
            $result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1);
        } else {
            $result = (openssl_verify($data, base64_decode($sign), $res) === 1);
        }
        if (!$result) {
            file_put_contents(ROOT_D.'/log.txt', date('Y-m-d H:i:s', time()) . " 验签串:" . $data. "\r\n", FILE_APPEND);
            //exi('签名验证未通过','error');
        }
        return $result;
    }
    // 公钥验签
    public function verify($plainText, $sign)
    {
        $resource = openssl_pkey_get_public($this->publicKey());
        $result   = openssl_verify($plainText, base64_decode($sign), $resource);
        openssl_free_key($resource);
        if (!$result) {
            exi('签名验证未通过！','error');
        }
        return $result;
    }
    /*
        发起接口请求
    */
    private function request($api_url,$package,$method='',$msg='',$isres=false){
        $postData = $this->postData($method,$package);
        $res=$this->httpPost($api_url, $postData);
        //print_r($res);exit;
        if(is_error($res)){
            if($msg){
                return error($res['message']);
            }else{
                exi($res['message'].'，请联系管理员设置好参数！','error');
            }
        }
        $res = $this->parseResult($res);
        //print_r($res);exit;
        //file_put_contents(ROOT_D.'/log.txt', date('Y-m-d H:i:s', time()) . " RES:" . $res['data']. "\r\n", FILE_APPEND);
        $res=json_decode($res['data'],true);
        if($res['head']['respCode']!='000000'){
            if($isres){
                return $res;
            }
            if($msg){
                return error($res['head']['respMsg']);
            }else{
                exi($res['head']['respMsg'].'，错误码：'.$res['head']['respCode'],'error');
            }
        }
        // 验签 & 返回结果
        //$verifyFlag = $this->verify($res['data'], $res['sign']);
        return $res['body'];
    }
    // 参数
    public function postData($method,$package){
        if($package['payMode']=='sand_wx'){
            if($_SESSION['wxapp_id']){
                $productId='00002021';
            }else{
                $productId='00002020';
            }
        }elseif($package['payMode']=='sand_alipay'){
            if($_SESSION['aliapp_id']){
                $productId='00002023';
            }else{
                $productId='00002022';
            }
        }elseif($package['payMode']=='sand_h5'){
            $productId='00000008';
        }
        $data = array(
            'head' => array(
                'version'     => '1.0',
                'method'      => $method,
                'productId'   => $productId,
                'accessType'  => '1',
                'mid'         => $this->config['sandpay_mid'],
                'plMid'       => '',
                'channelType' => '07',
                'reqTime'     => date('YmdHis', time()),
            ),
            'body' => $package,
        );
        $postData = array(
            'charset'  => 'utf-8',
            'signType' => '01',
            'data'     => json_encode($data),
            'sign'     => $this->sign($data),
        );
        return $postData;
    }
    // curl. 发送请求
    public function httpPost($url, $params){
        if (empty($url) || empty($params)) {
            exi('请求参数错误！','error');
        }
        $params = http_build_query($params);
        try {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            $data  = curl_exec($ch);
            $err   = curl_error($ch);
            $errno = curl_errno($ch);
            if ($errno) {
                $msg = 'curl errInfo: ' . $err . ' curl errNo: ' . $errno;
                throw new \Exception($msg);
            }
            curl_close($ch);
            return $data;
        } catch (\Exception $e) {
            if ($ch) curl_close($ch);
            throw $e;
        }
    }
    // curl.解析返回数据
    protected function parseResult($result)
    {
        $arr      = array();
        $response = urldecode($result);
        $arrStr   = explode('&', $response);
        foreach ($arrStr as $str) {
            $p         = strpos($str, "=");
            $key       = substr($str, 0, $p);
            $value     = substr($str, $p + 1);
            $arr[$key] = $value;
        }

        return $arr;
    }
}